/**
 * 
 * Copyright (C) 2011 Cody Stoutenburg . All rights reserved.
 *
 *       This program is free software; you can redistribute it and/or
 *       modify it under the terms of the GNU Lesser General Public License
 *       as published by the Free Software Foundation; either version 2.1
 *       of the License, or (at your option) any later version.
 *
 *       This program is distributed in the hope that it will be useful,
 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *       GNU Lesser General Public License for more details.
 *
 *       You should have received a copy of the GNU Lesser General Public License
 *       along with this program; if not, write to the Free Software
 *       Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 * 
 */
package ca.usherbrooke.goal;

import java.util.ArrayList;

import ca.usherbrooke.model.world.IWorldModel;

/**
 * @author Cody Stoutenburg
 * 
 */
public abstract class IGoal {

	public static String getGeneralDescription() {
		return "";
	}

	public static final String ARGS_SEPARATOR = " ";

	private BasicGoal realGoal;
	protected transient int argsCount;

	public IGoal() {
	}

	/**
	 * this method should return a unique name of goal for each goal class, this
	 * allow agent to identify kind of goal and to do specific work for each
	 * goals
	 * 
	 * @return
	 */
	public abstract String getName();

	/**
	 * 
	 * @param goal
	 * @param args
	 */
	public final void initGoal(BasicGoal goal, String args) {
		realGoal = goal;
		argsCount = 0;
		initGoal(args);
	}

	/**
	 * the goal should'nt have params in constructor so you can initialize here
	 * 
	 * @param args
	 *            the arguments of the goal
	 */
	protected abstract void initGoal(String args);

	// ///////////// SETTER GOAL ////////////////
	protected final void addSubGoal(BasicGoal goal) {
		realGoal.addSubGoal(goal);
	}

	// ///////////// GETTER GOAL ////////////////
	protected final Long getEntity() {
		return realGoal.getEntity();
	}

	protected final Float getScoreTotal() {
		return realGoal.getScoreTotal();
	}

	protected final Float getScoreFail() {
		return realGoal.getScoreFail();
	}

	protected final Boolean isTerminated() {
		return realGoal.isTerminated();
	}

	protected final Boolean isSolve() {
		return realGoal.isSolve();
	}

	protected final Boolean isFail() {
		return realGoal.isFail();
	}

	protected final Long getDuration() {
		return realGoal.getDuration();
	}

	protected final Long getRemainingTime() {
		return realGoal.getRemainingTime();
	}

	/**
	 * this methods is call when someone want to add a subgoal<br/>
	 * the default implementation just add the goal
	 * 
	 * @param goal
	 */
	public void askAddSubGoal(BasicGoal goal) {
		addSubGoal(goal);
	}

	/**
	 * this method is call to know if the goal is solve.<br/>
	 * The method is call only if the goal is not already solve or fail.
	 * 
	 * @param world
	 * @return true if the goal is solve
	 */
	public abstract Boolean verifyGoalSolve(IWorldModel world);

	/**
	 * 
	 * this method is call to know if the goal is fail. The method is call only
	 * if the goal is not already solve or fail.
	 * 
	 * @param world
	 * @return true if the goal is fail
	 */
	public abstract Boolean verifyGoalFail(IWorldModel world);

	/**
	 * get the estimated distance to solve the goal
	 * 
	 * @param world
	 *            a complete world
	 * @return the estimated distance to solve the goal
	 */
	public abstract Float calculateEstimatedDistance(IWorldModel world);

	/**
	 * get the representation of the goal in <code>Float</code> values
	 * 
	 * @return the representation of the goal in <code>Float</code> values
	 */
	public abstract ArrayList<Float> getValues();

	/**
	 * get the description of the goal
	 * 
	 * @return the description of the goal
	 */
	public abstract String getDescription();

	private Float calculateScore() {
		Float scoreFail = getScoreFail();
		Float scoreTotal = getScoreTotal();
		Float currentScore = scoreTotal;

		if (isSolve()) {
			currentScore = scoreTotal;
		}

		// event if the goal is solve we calculate score from time elapse
		if (!BasicGoal.INFINITY_TIME.equals(getDuration())) {
			Float score = scoreTotal - scoreFail;
			Long duration = getDuration();
			Long remainingTime = getRemainingTime();

			Float scoreFactor = remainingTime.floatValue()
					/ duration.floatValue();
			currentScore = (score * scoreFactor) + scoreFail;
		}

		if (isFail()) {
			currentScore = scoreFail;
		}

		return currentScore;
	}

	/**
	 * Default implementation of getCurrentScore <br/>
	 * if the duration is infinity <br/>
	 * - if fail return fail score <br/>
	 * - if solve return solve score <br/>
	 * - if not finish return 0<br/>
	 * <br/>
	 * <br/>
	 * if the duration is not infinity <br/>
	 * - if not finish return 0 <br/>
	 * - else the score is a ratio between the time taken and the score total
	 * and the score fail
	 * 
	 * @return
	 */
	public Float getCurrentScore() {
		Float currentScore = 0f;

		if (isTerminated()) {
			currentScore = calculateScore();
		}

		return currentScore;
	}

	/**
	 * Default implementation of getEstimatedScore <br/>
	 * if the duration is infinity <br/>
	 * - if fail return fail score <br/>
	 * - if solve return solve score <br/>
	 * - if not finish return solve score<br/>
	 * <br/>
	 * <br/>
	 * if the duration is not infinity <br/>
	 * the score is a ratio between the time taken and the score total and the
	 * score fail
	 * 
	 * @return
	 */
	public Float getEstimatedScore() {
		return calculateScore();
	}
}
